package org.summerframework.component.security.core.interceptor;

import org.summerframework.component.security.LoginData;
import org.summerframework.component.security.core.SecurityResultCode;
import org.summerframework.component.security.annotion.LoginRequired;
import org.summerframework.component.security.annotion.RoleRequired;
import org.summerframework.component.security.core.config.LoginProperties;
import org.summerframework.component.security.context.RequestContextFactory;
import org.summerframework.component.security.core.role.RoleService;
import org.summerframework.core.base.result.DefaultResultCode;
import org.summerframework.core.exception.WebException;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 登录拦截器
 *
 * @author 石超
 * @version v1.0.0
 */

@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class SecurityInterceptor extends AbstractContextHandlerInterceptorAdapter {
    public static final String ANY = "ANY";
    public static final String ADMIN = "ADMIN";

    @Autowired(required = false)
    private List<RoleService> roleServices;

    @Resource
    private LoginProperties loginProperties;

    private Map<String, List<RoleService>> CHCHE_ROLE_SERVICE = new ConcurrentHashMap<String, List<RoleService>>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;

        LoginRequired loginRequired = findAnnotation(handlerMethod, LoginRequired.class);
        RoleRequired roleRequired = findAnnotation(handlerMethod, RoleRequired.class);

        if (loginRequired != null) {
            LoginData loginData = RequestContextFactory.getLoginData(true);

            if (!loginData.isLogin()) {
                if (isHtml(handlerMethod)) {
                    redirect(loginProperties.redirectLogin(request));
                    return true;
                }

                throw new WebException(SecurityResultCode.ERROR_NOT_LOGIN);
            } else if (loginData.getStatus() == 0) {
                throw new WebException(SecurityResultCode.ERROR_VERIFICTION_FREEZE_EVER);
            }

            @SuppressWarnings("unchecked")
            Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);

            for (String name : loginRequired.userIdParamNames()) {
                if (uriTemplateVars.get(name) != null) {
                    throw new WebException(DefaultResultCode.ERROR_URL_REQUEST, "name=" + name + ",value=" + uriTemplateVars.get(name));
                }

                uriTemplateVars.put(name, String.valueOf(loginData.getUserId()));
            }

            for (String name : loginRequired.usernameParamNames()) {
                if (uriTemplateVars.get(name) != null) {
                    throw new WebException(DefaultResultCode.ERROR_URL_REQUEST, "name=" + name + ",value=" + uriTemplateVars.get(name));
                }

                uriTemplateVars.put(name, String.valueOf(loginData.getUserName()));
            }
        }

        if (roleServices == null) {
            return true;
        }

        List<String> roles = getRoles(handlerMethod.getBean().getClass(), handlerMethod.getMethod());
        boolean isAllRole = false;

        if (roleRequired != null) {
            isAllRole = roleRequired.isAllRole();
        }

        Exception exception = null;

        for (String role : roles) {
            List<RoleService> list = CHCHE_ROLE_SERVICE.get(role);

            if (list == null) {
                list = new ArrayList<>();

                //权限处理
                for (RoleService roleService : roleServices) {
                    if (roleService.supportsRole(role)) {
                        list.add(roleService);
                    }
                }

                OrderComparator.sort(list);
                CHCHE_ROLE_SERVICE.put(role, list);
            }

            for (RoleService roleService : list) {
                try {
                    if (roleService.isRole(role, handlerMethod)) {
                        if (!isAllRole) {
                            RequestContextFactory.getCurrentContext().setIsAny(role.equals(ANY));
                            RequestContextFactory.getCurrentContext().setPermissions(role);
                            return true;
                        }
                    } else {
                        if (isAllRole) {
                            return false;
                        }
                    }
                } catch (Exception e) {
                    if (isAllRole) {
                        throw e;
                    } else {
                        exception = e;
                    }
                }
            }
        }

        if (!isAllRole && exception != null) {
            if (exception instanceof WebException) {
                throw exception;
            } else {
                throw new WebException(DefaultResultCode.ERROR_REQUEST_NOT_ACCEPTABLE, "权限不足");
            }
        }

        return true;
    }

    private List<String> getRoles(Class<?> c, Method method) {
        String[] roles = new String[]{};

        roles = addRoles(roles, findAnnotation(c, method, LoginRequired.class));
        roles = addRoles(roles, findAnnotation(c, method, RoleRequired.class));

        return Arrays.asList(roles);
    }

    private String[] addRoles(String[] roles, LoginRequired loginRequired) {
        if (loginRequired != null) {
            return ArrayUtils.addAll(roles, loginRequired.role());
        } else {
            return roles;
        }
    }

    private String[] addRoles(String[] roles, RoleRequired roleRequired) {
        if (roleRequired != null) {
            return ArrayUtils.addAll(roles, roleRequired.value());
        } else {
            return roles;
        }
    }
}
